package biz

import (
	"context"
	"fmt"
	"gitee.com/shuokeyun/kratos/errors"
	"github.com/fullstorydev/grpcurl"
	"github.com/go-kratos/kratos/v2/metadata"
	"github.com/jhump/protoreflect/desc"
	"io"
	"strings"
	"time"
)

type Proxy struct {
	urlMap map[string]struct{}
	source grpcurl.DescriptorSource
	curl   *grpcCurl
	manage *GrpcClientConnManage
}

func NewProxy(manage *GrpcClientConnManage, source grpcurl.DescriptorSource) *Proxy {
	proxy := &Proxy{
		urlMap: make(map[string]struct{}),
		source: source,
		curl:   &grpcCurl{},
		manage: manage,
	}
	proxy.curl.source = proxy.source
	return proxy
}

func (p *Proxy) Forward(serviceName, symbol string, reader io.Reader) (interface{}, error) {
	if _, ok := p.urlMap[symbol]; !ok {
		svc, mth := parseSymbol(symbol)
		dsc, err := p.source.FindSymbol(svc)
		if err != nil {
			return nil, err
		}
		sd, ok := dsc.(*desc.ServiceDescriptor)
		if !ok {
			return nil, errors.New(404, "NOT_FOUND", fmt.Sprintf("target server does not expose service %q", svc))
		}
		mtd := sd.FindMethodByName(mth)
		if mtd == nil {
			return nil, errors.New(404, "NOT_FOUND", fmt.Sprintf("service %q does not include a method named %q", svc, mth))
		}
		p.urlMap[symbol] = struct{}{}
	}
	conn, ok := p.manage.GetClientConn(serviceName)
	if !ok {
		return nil, errors.New(404, "NOT_FOUND", fmt.Sprintf("server %s not found", serviceName))
	}
	b, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	str := fmt.Sprintf(`{"metadata":[],"data":[%s]}`, b)
	ctx, _ := context.WithTimeout(context.Background(), time.Second*1)
	ctx = metadata.AppendToClientContext(ctx, "x-md-global-am-id", "110000")
	ctx = metadata.AppendToClientContext(ctx, "x-md-global-am-uid", "1")
	ctx = metadata.AppendToClientContext(ctx, "x-md-global-app-id", "10000")
	ctx = metadata.AppendToClientContext(ctx, "x-md-global-price-area", "33")
	return p.curl.InvokeRPC(ctx, conn, symbol, []byte(str))
}

func parseSymbol(svcAndMethod string) (string, string) {
	pos := strings.LastIndex(svcAndMethod, "/")
	if pos < 0 {
		pos = strings.LastIndex(svcAndMethod, ".")
		if pos < 0 {
			return "", ""
		}
	}
	return svcAndMethod[:pos], svcAndMethod[pos+1:]
}
